{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![Banner](images/banner.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# DML - INSERT, UPDATE, DELETE, and MERGE Statements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<hr>\n",
    "\n",
    "Setup for this notebook:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import oracledb\n",
    "\n",
    "un = os.environ.get(\"PYO_SAMPLES_MAIN_USER\", \"pythondemo\")\n",
    "pw = os.environ.get(\"PYO_SAMPLES_MAIN_PASSWORD\", \"welcome\")\n",
    "cs = os.environ.get(\"PYO_SAMPLES_CONNECT_STRING\", \"localhost/orclpdb\")\n",
    "\n",
    "connection = oracledb.connect(user=un, password=pw, dsn=cs)\n",
    "\n",
    "cursor = connection.cursor()\n",
    "try:\n",
    "    cursor.execute(\"drop table mytab\")\n",
    "except:\n",
    "    pass\n",
    "cursor.execute(\"create table mytab (id number, data varchar2(1000))\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Binding for Insertion\n",
    "\n",
    "Documentation reference link: [Using Bind Variables](https://python-oracledb.readthedocs.io/en/latest/user_guide/bind.html)\n",
    "\n",
    "Binding is very, very important. \n",
    "\n",
    "Binding:\n",
    "- eliminates escaping special characters and helps prevent SQL injection attacks\n",
    "- is important for performance and scalability"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Done\n"
     ]
    }
   ],
   "source": [
    "cursor.execute(\"truncate table mytab\")\n",
    "\n",
    "sql = \"insert into mytab (id, data) values (:idVal, :dataVal)\"\n",
    "\n",
    "# bind by position using a sequence (list or tuple)\n",
    "cursor.execute(sql, [1, \"String 1\"])\n",
    "cursor.execute(sql, (2, \"String 2\"))\n",
    "\n",
    "# bind by name using a dictionary\n",
    "cursor = connection.cursor()\n",
    "cursor.execute(sql, {\"idVal\": 3, \"dataVal\": \"String 3\"})\n",
    "\n",
    "# bind by name using keyword arguments\n",
    "cursor.execute(sql, idVal=4, dataVal=\"String 4\")\n",
    "\n",
    "print(\"Done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Batch execution - Inserting multiple rows with executemany()\n",
    "\n",
    "Documentation reference link: [Executing Batch Statements and Bulk Loading](https://python-oracledb.readthedocs.io/en/latest/user_guide/batch_statement.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 'First')\n",
      "(2, 'Second')\n",
      "(3, 'Third')\n",
      "(4, 'Fourth')\n",
      "(5, 'Fifth')\n",
      "(6, 'Sixth')\n",
      "(7, 'Seventh')\n"
     ]
    }
   ],
   "source": [
    "cursor.execute(\"truncate table mytab\")\n",
    "\n",
    "rows = [ (1, \"First\" ),\n",
    "\t (2, \"Second\" ),\n",
    "\t (3, \"Third\" ),\n",
    "\t (4, \"Fourth\" ),\n",
    "\t (5, \"Fifth\" ),\n",
    "\t (6, \"Sixth\" ),\n",
    "\t (7, \"Seventh\" ) ]\n",
    "\n",
    "# Using setinputsizes helps avoid memory reallocations.\n",
    "# The parameters correspond to the insert columns.  \n",
    "# The value None says use python-oracledb's default size for a NUMBER column.  \n",
    "# The second value is the maximum input data (or column) width for the VARCHAR2 column\n",
    "cursor.setinputsizes(None, 7)\n",
    "\n",
    "cursor.executemany(\"insert into mytab(id, data) values (:1, :2)\", rows)\n",
    "\n",
    "# Now query the results back\n",
    "\n",
    "for row in cursor.execute('select * from mytab'):\n",
    "    print(row)\n",
    "\n",
    "connection.rollback()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Performance - executemany() vs execute()\n",
    "\n",
    "This script compares `executemany()` vs `execute()` and shows that `executemany()` is faster for inserting multiple records.  It may take several seconds for it to run and plot the graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "execute() loop      1 rows in 0.0276 seconds\n",
      "executemany()       1 rows in 0.0064 seconds\n",
      "execute() loop      5 rows in 0.0259 seconds\n",
      "executemany()       5 rows in 0.0065 seconds\n",
      "execute() loop     10 rows in 0.0554 seconds\n",
      "executemany()      10 rows in 0.0081 seconds\n",
      "execute() loop    100 rows in 0.4570 seconds\n",
      "executemany()     100 rows in 0.0207 seconds\n",
      "execute() loop   1000 rows in 3.9817 seconds\n",
      "executemany()    1000 rows in 0.1517 seconds\n",
      "execute() loop   5000 rows in 20.7784 seconds\n",
      "executemany()    5000 rows in 0.7431 seconds\n",
      "Plot is:\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import time\n",
    "\n",
    "cursor = connection.cursor()\n",
    "cursor.execute(\"truncate table mytab\")\n",
    "\n",
    "# Row counts to test inserting\n",
    "numrows = (1, 5, 10, 100, 1000)\n",
    "\n",
    "longstring = \"x\" * 1000\n",
    "\n",
    "def create_data(n):\n",
    "    d = []\n",
    "    for i in range(n):\n",
    "        d.append((i, longstring))\n",
    "    return d\n",
    "\n",
    "ex = []  # seconds for execute() loop\n",
    "em = []  # seconds for executemany()\n",
    "\n",
    "for n in numrows:\n",
    "    \n",
    "    rows = create_data(n)\n",
    "    \n",
    "    ############################################################\n",
    "    #\n",
    "    # Loop over each row\n",
    "    #\n",
    "\n",
    "    start=time.time()\n",
    "\n",
    "    for r in rows:\n",
    "        cursor.execute(\"insert into mytab(id, data) values (:1, :2)\", r)          # <==== Loop over execute()\n",
    "        \n",
    "    elapsed = time.time() - start\n",
    "    ex.append(elapsed)\n",
    "    \n",
    "    r, = cursor.execute(\"select count(*) from mytab\").fetchone()\n",
    "    print(\"execute() loop {:6d} rows in {:06.4f} seconds\".format(r, elapsed))    \n",
    "    connection.rollback()\n",
    "    \n",
    "    ############################################################# \n",
    "    #\n",
    "    # Insert all rows in one call\n",
    "    #\n",
    "\n",
    "    start = time.time()\n",
    "\n",
    "    cursor.executemany(\"insert into mytab(id, data) values (:1, :2)\", rows)       # <==== One executemany()\n",
    "    \n",
    "    elapsed = time.time() - start\n",
    "    em.append(elapsed)\n",
    "    \n",
    "    r, = cursor.execute(\"select count(*) from mytab\").fetchone()\n",
    "    print(\"executemany()  {:6d} rows in {:06.4f} seconds\".format(r, elapsed))  \n",
    "    connection.rollback()\n",
    "\n",
    "\n",
    "print(\"Plot is:\")\n",
    "plt.xticks(numrows)\n",
    "plt.plot(numrows, ex, label=\"execute() loop\", marker=\"o\")\n",
    "plt.plot(numrows, em, label=\"one executemany()\", marker=\"o\")\n",
    "plt.xscale(\"log\")\n",
    "plt.xlabel('Number of rows')\n",
    "plt.ylabel('Seconds')\n",
    "plt.legend(loc=\"upper left\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Noisy Data - Batch Errors\n",
    "\n",
    "Dealing with bad data is easy with the `batcherrors` parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(10, 'Parent 10')\n",
      "(20, 'Parent 20')\n",
      "(30, 'Parent 30')\n",
      "(40, 'Parent 40')\n",
      "(50, 'Parent 50')\n",
      "(1001, 10, 'Child A of Parent 10')\n",
      "(1002, 20, 'Child A of Parent 20')\n",
      "(1003, 20, 'Child B of Parent 20')\n",
      "(1004, 20, 'Child C of Parent 20')\n",
      "(1005, 30, 'Child A of Parent 30')\n",
      "(1006, 30, 'Child B of Parent 30')\n",
      "(1007, 40, 'Child A of Parent 40')\n",
      "(1008, 40, 'Child B of Parent 40')\n",
      "(1009, 40, 'Child C of Parent 40')\n",
      "(1010, 40, 'Child D of Parent 40')\n",
      "(1011, 40, 'Child E of Parent 40')\n",
      "(1012, 50, 'Child A of Parent 50')\n",
      "(1013, 50, 'Child B of Parent 50')\n",
      "(1014, 50, 'Child C of Parent 50')\n",
      "(1015, 50, 'Child D of Parent 50')\n",
      "(1016, 10, 'Child Red')\n",
      "(1018, 20, 'Child Blue')\n",
      "(1022, 40, 'Child Yellow')\n"
     ]
    }
   ],
   "source": [
    "# Initial data\n",
    "\n",
    "cursor.execute(\"truncate table mytab\")\n",
    "\n",
    "for row in cursor.execute(\"select * from ParentTable order by ParentId\"):\n",
    "    print(row)\n",
    "\n",
    "for row in cursor.execute(\"select * from ChildTable order by ChildId\"):\n",
    "    print(row)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Additional data, some of it invalid:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Errors in rows that were not inserted:\n",
      "\n",
      "Error ORA-00001: unique constraint (PYTHONDEMO.CHILDTABLE_PK) violated on table PYTHONDEMO.CHILDTABLE columns (CHILDID)\n",
      "ORA-03301: (ORA-00001 details) row with column values (CHILDID:1016) already exists\n",
      "Help: https://docs.oracle.com/error-help/db/ora-00001/ at row offset 0\n",
      "Error ORA-00001: unique constraint (PYTHONDEMO.CHILDTABLE_PK) violated on table PYTHONDEMO.CHILDTABLE columns (CHILDID)\n",
      "ORA-03301: (ORA-00001 details) row with column values (CHILDID:1018) already exists\n",
      "Help: https://docs.oracle.com/error-help/db/ora-00001/ at row offset 1\n",
      "Error ORA-00001: unique constraint (PYTHONDEMO.CHILDTABLE_PK) violated on table PYTHONDEMO.CHILDTABLE columns (CHILDID)\n",
      "ORA-03301: (ORA-00001 details) row with column values (CHILDID:1018) already exists\n",
      "Help: https://docs.oracle.com/error-help/db/ora-00001/ at row offset 2\n",
      "Error ORA-00001: unique constraint (PYTHONDEMO.CHILDTABLE_PK) violated on table PYTHONDEMO.CHILDTABLE columns (CHILDID)\n",
      "ORA-03301: (ORA-00001 details) row with column values (CHILDID:1022) already exists\n",
      "Help: https://docs.oracle.com/error-help/db/ora-00001/ at row offset 3\n",
      "Error ORA-02291: integrity constraint (PYTHONDEMO.CHILDTABLE_FK) violated - parent key not found\n",
      "Help: https://docs.oracle.com/error-help/db/ora-02291/ at row offset 4\n",
      "\n",
      "Rows that were successfully inserted:\n",
      "\n",
      "(1001, 10, 'Child A of Parent 10')\n",
      "(1002, 20, 'Child A of Parent 20')\n",
      "(1003, 20, 'Child B of Parent 20')\n",
      "(1004, 20, 'Child C of Parent 20')\n",
      "(1005, 30, 'Child A of Parent 30')\n",
      "(1006, 30, 'Child B of Parent 30')\n",
      "(1007, 40, 'Child A of Parent 40')\n",
      "(1008, 40, 'Child B of Parent 40')\n",
      "(1009, 40, 'Child C of Parent 40')\n",
      "(1010, 40, 'Child D of Parent 40')\n",
      "(1011, 40, 'Child E of Parent 40')\n",
      "(1012, 50, 'Child A of Parent 50')\n",
      "(1013, 50, 'Child B of Parent 50')\n",
      "(1014, 50, 'Child C of Parent 50')\n",
      "(1015, 50, 'Child D of Parent 50')\n",
      "(1016, 10, 'Child Red')\n",
      "(1018, 20, 'Child Blue')\n",
      "(1022, 40, 'Child Yellow')\n"
     ]
    }
   ],
   "source": [
    "dataToInsert = [\n",
    "    (1016, 10, 'Child Red'),\n",
    "    (1018, 20, 'Child Blue'),\n",
    "    (1018, 30, 'Child Green'),  # duplicate key\n",
    "    (1022, 40, 'Child Yellow'),\n",
    "    (1021, 75, 'Child Orange')  # parent does not exist\n",
    "]\n",
    "    \n",
    "cursor.executemany(\"insert into ChildTable values (:1, :2, :3)\", dataToInsert, batcherrors=True)\n",
    "\n",
    "print(\"\\nErrors in rows that were not inserted:\\n\")\n",
    "for error in cursor.getbatcherrors():\n",
    "    print(\"Error\", error.message, \"at row offset\", error.offset)   \n",
    "\n",
    "print(\"\\nRows that were successfully inserted:\\n\")\n",
    "for row in cursor.execute(\"select * from ChildTable order by ChildId\"):\n",
    "    print(row)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now you can choose whether or not to fix failed records and reinsert them.\n",
    "You can then rollback or commit.\n",
    "\n",
    "This is true even if you had enabled autocommit mode - no commit will occur if there are batch errors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Roll back so the example can be re-run\n",
    "connection.rollback()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Direct Path Loads\n",
    "\n",
    "Documentation reference link: [Direct Path Loads](https://python-oracledb.readthedocs.io/en/latest/user_guide/batch_statement.html#direct-path-loads)\n",
    "\n",
    "Direct Path Loading is an Oracle Database feature most commonly known from SQL*Loader. It is efficient for ingesting very large data sets into Oracle Database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCHEMA_NAME = \"pythondemo\"\n",
    "TABLE_NAME = \"mytab\"\n",
    "COLUMN_NAMES = [\"id\", \"data\"]\n",
    "DATA = [\n",
    "    (1, \"A first row\"),\n",
    "    (2, \"A second row\"),\n",
    "    (3, \"A third row\")\n",
    "]\n",
    "\n",
    "connection.direct_path_load(\n",
    "    schema_name=SCHEMA_NAME,\n",
    "    table_name=TABLE_NAME,\n",
    "    column_names=COLUMN_NAMES,\n",
    "    data=DATA\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Direct Path Loads are efficient because some Oracle Database code layers are bypassed. There are also no INSERT statements involved. This means there are a few restrictions on when to use the feature: refer to Oracle Database documentation such as SQL*Loader [Direct Path Loads](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-0D576DEF-7918-4DD2-A184-754D217C021F)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Verify inserted data\n",
    "\n",
    "for row in cursor.execute('select * from mytab'):\n",
    "    print(row)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the data is automatically committed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Remove the data so the sample can be re-run cleanly\n",
    "\n",
    "cursor.execute(\"truncate table mytab\")\n",
    "connection.commit()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.24"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
